/**
 * 
 */
package com.dalonedrow.engine.ui.impl;

import com.dalonedrow.engine.systems.base.ProjectConstants;
import com.dalonedrow.engine.ui.base.GuiComponentImpl;
import com.dalonedrow.engine.ui.base.GuiComponentLibrary;
import com.dalonedrow.globalvariables.constants.hq.GlobalConstants;
import com.dalonedrow.utils.UtilityMethods;

/**
 * @author Donald
 */
public abstract class PanningMap extends GuiComponentImpl {
	/** the size of the buffer zone for panning cursors. */
	private int		edge;
	/** the flags. */
	private long	flags;
	/** the component's id. */
	private int		id;
	/** the height of the map in pixels. */
	private int		mapHeight;
	/** the height of the map in pixels. */
	private int		mapWidth;
	/** the current map mode. */
	private int		mode;
	/** flag indicating that the map should be panning in a direction. */
	private int		panning;
	/** the last direction the map was panning in. */
	private int		panningDirection;
	/** the time the user hovered over the item. */
	private long	panningShiftTimer;
	/** the time the user hovered over the item. */
	private long	panningTimer;
	/** the viewport's height. */
	private int		viewportHeight;
	/** the viewport's width. */
	private int		viewportWidth;
	/** the viewport's current x-position. */
	private int		viewX;
	/** the viewport's current y-position. */
	private int		viewY;
	/** Protected constructor. */
	protected PanningMap() {
		id = GuiComponentLibrary.getInstance().getNextId();
		mode = GlobalConstants.MAP_MODE_00_FREE;
		edge = GlobalConstants.MAP_EDGE;
		panning = GlobalConstants.DIR_NONE;
		panningShiftTimer = 0L;
		flags = 0;
		GuiComponentLibrary.getInstance().addComponent(this);
	}
	/**
	 * Adjusts the value of the viewport's current x-position.
	 * @param val the amount to adjust by
	 */
	protected final void adjustViewX(final int val) {
		viewX += val;
	}
	/**
	 * Adjusts the value of the viewport's current y-position.
	 * @param val the amount to adjust by
	 */
	protected final void adjustViewY(final int val) {
		viewY += val;
	}
	/**
	 * Assigns a flag.
	 * @param flag the flag
	 */
	protected final void assignFlag(final long flag) {
		flags |= flag;
	}
	/**
	 * Centers the map on a specific IO.
	 * @param refid the IO's reference id
	 */
	public abstract void centerMapOnIO(int refid);
	/**
	 * Checks to see if panning is possible, and assigns the direction and
	 * cursor rendered. Panning is not possible when the map has reached its
	 * borders.
	 * @param x the x-coordinate of the mouse pointer
	 * @param y the y-coordinate of the mouse pointer
	 * @return true if panning is possible; false otherwise
	 */
	protected final boolean checkForPanning(final float x, final float y) {
		boolean panPossible = false;
		// the inner left edge of the panning zone
		double ltinrx = super.getPosition().getX();
		// the outer left edge of the panning zone
		double ltotrx = super.getPosition().getX();
		// the inner right edge of the panning zone
		double rtinrx = super.getPosition().getX();
		// the outer left edge of the panning zone
		double rtotrx = super.getPosition().getX();
		// move left from border insets
		if (super.getBorder() != null) {
			ltinrx += super.getBorder().getInsets().left;
			ltotrx += super.getBorder().getInsets().left;
			rtinrx += super.getBorder().getInsets().left;
			rtotrx += super.getBorder().getInsets().left;
		}
		rtinrx += viewportWidth;
		rtotrx += viewportWidth;
		ltinrx += edge;
		rtinrx -= edge;
		// the inner bottom edge of the panning zone
		double btinry = super.getPosition().getY();
		// the outer bottom edge of the panning zone
		double btotry = super.getPosition().getY();
		// the inner top edge of the panning zone
		double tpinry = super.getPosition().getY();
		// the outer top edge of the panning zone
		double tpotry = super.getPosition().getY();
		// move up from border insets
		if (super.getBorder() != null) {
			btinry += super.getBorder().getInsets().bottom;
			btotry += super.getBorder().getInsets().bottom;
			tpinry += super.getBorder().getInsets().bottom;
			tpotry += super.getBorder().getInsets().bottom;
		}
		tpinry += viewportHeight;
		tpotry += viewportHeight;
		btinry += edge;
		tpinry -= edge;
		// determine which edge the mouse is on?
		if (x < ltinrx
				&& x >= ltotrx) {
			// the mouse is between the left outer edge and inner panning area
			// NNW, SSW, W edges
			if (y < btinry
					&& y >= btotry) {
				// the mouse is between the bottom outer edge and inner area
				// SSW - is it possible to pan SSW?
				if (viewX > 0
						&& viewY > 0) {
					// as long as the view isn't at the left-most, bottom-most
					// position, you can pan SSW
					panPossible = true;
					panningDirection = GlobalConstants.DIR_05_SOUTH_WEST;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_26_PAN_SOUTHWEST);
				} else if (viewX > 0) {
					// view has already reached bottom-most
					// position, you can pan W
					panPossible = true;
					panningDirection = GlobalConstants.DIR_06_WEST;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_27_PAN_WEST);
				} else if (viewY > 0) {
					// view has already reached left-most
					// position, you can pan S
					panPossible = true;
					panningDirection = GlobalConstants.DIR_04_SOUTH;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_25_PAN_SOUTH);
				}
			} else if (y >= tpinry
					&& y < tpotry) {
				// the mouse is between the top outer edge and inner area
				// NNW
				if (viewX > 0
						&& viewY < mapHeight - viewportHeight) {
					// as long as the view isn't at the left-most, top-most
					// position, you can pan NNW
					panPossible = true;
					panningDirection = GlobalConstants.DIR_07_NORTH_WEST;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_20_PAN_NORTHWEST);
				} else if (viewX > 0) {
					// view has already reached top-most
					// position, you can pan W
					panPossible = true;
					panningDirection = GlobalConstants.DIR_06_WEST;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_27_PAN_WEST);
				} else if (viewY < mapHeight - viewportHeight) {
					// view has already reached left-most
					// position, you can pan N
					panPossible = true;
					panningDirection = GlobalConstants.DIR_00_NORTH;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_21_PAN_NORTH);
				}
			} else { // W
				// is it possible to go pan W
				if (viewX > 0) {
					// as long as the view isn't at the left-most
					// position, you can pan W
					panPossible = true;
					panningDirection = GlobalConstants.DIR_06_WEST;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_27_PAN_WEST);
				}
			}
		} else if (x >= rtinrx
				&& x < rtotrx) {
			// the mouse is between the right inner panning area and outer edge
			// NNE, SSE, E
			if (y >= btotry
					&& y < btinry) {
				// the mouse is between the bottom outer edge and inner area
				// SSE
				if (viewX < mapWidth - viewportWidth
						&& viewY > 0) {
					// as long as the view isn't at the right-most, bottom-most
					// position, you can pan SSE
					panPossible = true;
					panningDirection = GlobalConstants.DIR_03_SOUTH_EAST;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_24_PAN_SOUTHEAST);
				} else if (viewX < mapWidth - viewportWidth) {
					// view has already reached bottom-most
					// position, you can pan E
					panPossible = true;
					panningDirection = GlobalConstants.DIR_02_EAST;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_23_PAN_EAST);
				} else if (viewY > 0) {
					// view has already reached right-most
					// position, you can pan S
					panPossible = true;
					panningDirection = GlobalConstants.DIR_04_SOUTH;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_25_PAN_SOUTH);
				}
			} else if (y >= tpinry
					&& y < tpotry) {
				// the mouse is between the top outer edge and inner area
				// NNE
				if (viewX < mapWidth - viewportWidth
						&& viewY < mapHeight - viewportHeight) {
					// as long as the view isn't at the right-most, top-most
					// position, you can pan NNE
					panPossible = true;
					panningDirection = GlobalConstants.DIR_01_NORTH_EAST;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_22_PAN_NORTHEAST);
				} else if (viewX < mapWidth - viewportWidth) {
					// view has already reached top-most
					// position, you can pan E
					panPossible = true;
					panningDirection = GlobalConstants.DIR_02_EAST;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_23_PAN_EAST);
				} else if (viewY < mapHeight - viewportHeight) {
					// view has already reached right-most
					// position, you can pan N
					panPossible = true;
					panningDirection = GlobalConstants.DIR_00_NORTH;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_21_PAN_NORTH);
				}
			} else {
				// E
				if (viewX < mapWidth - viewportWidth) {
					// as long as the view isn't at the right-most
					// position, you can pan E
					panPossible = true;
					panningDirection = GlobalConstants.DIR_02_EAST;
					ProjectConstants.getInstance().setCursor(
							GlobalConstants.CURSOR_23_PAN_EAST);
				}
			}
		} else if (y >= btotry
				&& y < btinry) {
			// the mouse is between the bottom outer edge and inner area
			// S
			if (viewY > 0) {
				// as long as the view isn't at the bottom-most
				// position, you can pan S
				panPossible = true;
				panningDirection = GlobalConstants.DIR_04_SOUTH;
				ProjectConstants.getInstance().setCursor(
						GlobalConstants.CURSOR_25_PAN_SOUTH);
			}
		} else if (y >= tpinry
				&& y < tpotry) {
			// the mouse is between the top outer edge and inner area
			// N
			if (viewY < mapHeight - viewportHeight) {
				// as long as the view isn't at the top-most
				// position, you can pan N
				panPossible = true;
				panningDirection = GlobalConstants.DIR_00_NORTH;
				ProjectConstants.getInstance().setCursor(
						GlobalConstants.CURSOR_21_PAN_NORTH);
			}
		}
		return panPossible;
	}
	/** Clears all flags that were assigned. */
	public final void clearFlags() {
		flags = 0;
	}
	/**
	 * Gets the size of the buffer zone for panning cursors.
	 * @return <code>int</code>
	 */
	protected final int getEdge() {
		return edge;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final int getId() {
		return id;
	}
	/**
	 * Gets the height of the map in pixels.
	 * @return <code>int</code>
	 */
	protected final int getMapHeight() {
		return mapHeight;
	}
	/**
	 * Gets the width of the map in pixels.
	 * @return <code>int</code>
	 */
	protected final int getMapWidth() {
		return mapWidth;
	}
	/**
	 * Gets the current map mode.
	 * @return <code>int</code>
	 */
	public final int getMode() {
		return mode;
	}
	/**
	 * Gets the flag indicating whether the map should be panning in any
	 * direction.
	 * @return <code>int</code>
	 */
	protected final int getPanning() {
		return panning;
	}
	/**
	 * Gets the last direction the map was panning in.
	 * @return <code>int</code>
	 */
	protected final int getPanningDirection() {
		return panningDirection;
	}
	/**
	 * Gets the viewport's height.
	 * @return <code>int</code>
	 */
	protected final int getViewportHeight() {
		return viewportHeight;
	}
	/**
	 * Gets the viewport's width.
	 * @return <code>int</code>
	 */
	protected final int getViewportWidth() {
		return viewportWidth;
	}
	/**
	 * Gets the viewport's current x-position.
	 * @return <code>int</code>
	 */
	public final int getViewX() {
		return viewX;
	}
	/**
	 * Gets the viewport's current y-position.
	 * @return <code>int</code>
	 */
	public final int getViewY() {
		return viewY;
	}
	/**
	 * Handles events that occur when the map is clicked on while in action menu
	 * mode.
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 * @param pointer
	 * @param button
	 */
	protected void handleMCActionMenuMode(int x, int y, int pointer, int button) {
		// TODO Auto-generated method stub

	}
	/**
	 * Handles events that occur when the map is clicked on while in free mode.
	 * @param e the {@link MouseEvent}
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 * @throws Exception if an error occurs
	 */
	protected abstract void handleMCFreeMode(int x, int y, int pointer, int button)
			throws Exception;
	/**
	 * Handles events that occur when the map is clicked on while in move
	 * selection mode.
	 * @param e the {@link MouseEvent}
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 */
	protected abstract void handleMCMoveSelectionMode(int x, int y, int pointer, int button);
	/**
	 * Handles events that occur when the mouse is over the map while in action
	 * menu mode.
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 */
	protected abstract void handleMOActionMenuMode(final float x,
			final float y);
	/**
	 * Handles events that occur when the mouse is over the map while in free
	 * mode.
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 * @throws Exception if an error occurs
	 */
	protected abstract void handleMOFreeMode(final float x, final float y)
			throws Exception;
	/**
	 * Handles events that occur when the mouse is over the map while in action
	 * menu mode.
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 */
	protected abstract void handleMOMoveSelectionMode(final float x,
			final float y);
	/**
	 * Handles events that occur when the user hovers an item.
	 * @param x the x-coordinate
	 * @param y the y-coordinate
	 */
	public abstract void handleMouseOver(float x, float y);
	/**
	 * Determines if the {@link OverworldView} has a specific flag set.
	 * @param flag the flag
	 * @return true if the flag was set; false otherwise
	 */
	public final boolean hasFlag(final long flag) {
		return (flags & flag) == flag;
	}
	/**
	 * Determines if the panning shift timer has started. If true, the timer has
	 * started and when the cycle reaches its end, the map will pan in a
	 * direction; otherwise the cycle hasn't started.
	 * @return <code>boolean</code>
	 */
	protected final boolean hasPanningShiftTimerStarted() {
		return panningShiftTimer > 0L;
	}
	/**
	 * Determines if the map panning timer has started.
	 * @return true if the panning timer has a start time other than 0L; false
	 *         otherwise
	 */
	protected final boolean hasPanningTimerStarted() {
		return panningTimer > 0L;
	}
	/**
	 * Determines if the mouse pointer is within the edges of the map, which
	 * would make the map ready to shift.
	 * @param x the x-coordinate of the mouse pointer
	 * @param y the y-coordinate of the mouse pointer
	 * @return true if the pointer is in any one of the map edges; false
	 *         otherwise
	 */
	protected final boolean isMouseInEdge(final float x, final float y) {
		boolean isInEdge = true;
		// the inner left edge of the panning zone
		double ltinrx = super.getPosition().getX();
		// the inner right edge of the panning zone
		double rtinrx = super.getPosition().getX();
		// move left from border insets
		if (super.getBorder() != null) {
			ltinrx += super.getBorder().getInsets().left;
			rtinrx += super.getBorder().getInsets().left;
		}
		rtinrx += viewportWidth;
		ltinrx += edge;
		rtinrx -= edge;
		// the inner bottom edge of the panning zone
		double btinry = super.getPosition().getY();
		// the inner top edge of the panning zone
		double tpinry = super.getPosition().getY();
		// move up from border insets
		if (super.getBorder() != null) {
			btinry += super.getBorder().getInsets().bottom;
			tpinry += super.getBorder().getInsets().bottom;
		}
		tpinry += viewportHeight;
		btinry += edge;
		tpinry -= edge;
		if (x > ltinrx // in left edge
				&& x <= rtinrx // in right edge
				&& y > btinry
				&& y <= tpinry) {
			// mouse is inside of edge borders. panning not possible
			isInEdge = false;
		}
		return isInEdge;
	}
	/**
	 * Gets the reference id of any IO that the mouse is currently over. If the
	 * mouse isn't over an IO, a value of -1 is returned.
	 * @param x the mouse's x-position
	 * @param y the mouse's y-position
	 * @return <code>int</code>
	 */
	protected abstract int isMouseOverIO(float x, float y);
	/**
	 * Determines if the mouse is over an IO.
	 * @param refid the IO's reference id
	 * @param x the mouse's x-position
	 * @param y the mouse's y-position
	 * @return true if the mouse is over the IO; false otherwise
	 */
	protected abstract boolean isMouseOverIO(int refid, float x, float y);
	/** Pans the map. */
	protected abstract void pan();
	/**
	 * Plays animation whenever an IO is hovered.
	 * @param refId the reference ID of the IO
	 */
	protected abstract void playHighlightAnimation(int refId);
	/**
	 * Determines if the panning shift cycle has reached its end. If true, the
	 * timer has started and when the cycle reaches its end, the map will pan in
	 * a direction; otherwise no panning is done.
	 * @param time the current time
	 * @return <code>boolean</code>
	 */
	protected final boolean readyForPanShift(final long time) {
		boolean ready = false;
		if (hasPanningShiftTimerStarted()) {
			long now = panningShiftTimer
					+ UtilityMethods.getInstance().convertMilliToNano(
							GlobalConstants.PANNING_SHIFT_DELAY);
			if (time >= now) {
				ready = true;
			}
		}
		return ready;
	}
	/**
	 * Removes a flag.
	 * @param flag the flag
	 */
	public final void removeFlag(final long flag) {
		flags &= ~flag;
	}
	/**
	 * Sets the size of the buffer zone for panning cursors.
	 * @param val the size to set
	 */
	protected final void setEdge(final int val) {
		edge = val;
	}
	/**
	 * Sets the height of the map in pixels.
	 * @param val the height to set
	 */
	protected final void setMapHeight(final int val) {
		mapHeight = val;
	}
	/**
	 * Sets the width of the map in pixels.
	 * @param val the width to set
	 */
	protected final void setMapWidth(final int val) {
		mapWidth = val;
	}
	/**
	 * Sets the current map mode.
	 * @param newMode the mode to set
	 */
	public final void setMode(final int newMode) {
		mode = newMode;
		switch (mode) {
		case GlobalConstants.MAP_MODE_00_FREE:
			removeFlag(GlobalConstants.MAP_FLAG_PARTY_HOVER);
			break;
		case GlobalConstants.MAP_MODE_01_ACTION_MENU:
			assignFlag(GlobalConstants.MAP_FLAG_PARTY_HOVER);
			break;
		default:
			break;
		}
	}
	/**
	 * Sets the flag indicating whether the map should be panning in any
	 * direction.
	 * @param dir the direction to pan in
	 */
	protected final void setPanning(final int dir) {
		panning = dir;
	}
	/**
	 * Sets the last direction the map was panning in.
	 * @param dir the last direction
	 */
	protected final void setPanningDirection(final int dir) {
		panningDirection = dir;
	}
	/**
	 * Sets the time the user hovered over the item.
	 * @param time the time in nanoseconds
	 */
	protected final void setPanningShiftTimer(final long time) {
		panningShiftTimer = time;
	}
	/**
	 * Sets the viewport's height.
	 * @param val the height to set
	 */
	protected final void setViewportHeight(final int val) {
		viewportHeight = val;
	}
	/**
	 * Sets the viewport's width.
	 * @param val the width to set
	 */
	protected final void setViewportWidth(final int val) {
		viewportWidth = val;
	}
	/**
	 * Sets the viewport's current x-position.
	 * @param val the value to set
	 */
	protected final void setViewX(final int val) {
		viewX = val;
	}
	/**
	 * Sets the viewport's current y-position.
	 * @param val the value to set
	 */
	protected final void setViewY(final int val) {
		viewY = val;
	}
	/**
	 * Starts the panning time.
	 * @param time the time the timer starts, in nanoseconds
	 */
	protected final void startPanningTimer(final long time) {
		panningTimer = time;
	}
	public abstract boolean touchUp(int x, int y, int pointer, int button);
	/**
	 * Updates the panning timer.
	 * @param time the current time
	 */
	protected final void updatePanningTimer(final long time) {
		if (hasPanningTimerStarted()) {
			long now = panningTimer
					+ UtilityMethods.getInstance().convertMilliToNano(
							GlobalConstants.PANNING_START_DELAY);
			if (time >= now) {
				panning = panningDirection;
				if (!hasPanningShiftTimerStarted()) {
					setPanningShiftTimer(time);
				}
			}
		}
	}
}
